use actix_web::{HttpRequest, delete, get, post, put, web};

use log::{info, trace};
use rbase::{
    define::{ApiError, ApiResponse},
    id_utils::{opt_id2str, opt_str2id},
    str_utils::deserialize_strs,
};
use rbase_iam::rest_api::service::auth_check::get_user_from_request;
use serde::{Deserialize, Serialize};

use anyhow::Result;
use rbase::id_utils::{id2str, str2id};

use crate::{
    db::entities::knowledge::Model,
    rest_api::service::srv_knowledge::{self},
};

#[derive(Clone, Debug, Deserialize)]
pub struct CreateParams {
    pub name: Option<String>,
    pub subject_id: Option<String>,
    pub category_id: Option<String>,
    pub content: Option<String>,
    pub tag_ids: Option<Vec<String>>,
}

#[derive(Clone, Debug, Deserialize)]
pub struct UpdateParams {
    pub name: Option<String>,
    pub subject_id: Option<String>,
    pub category_id: Option<String>,
    pub content: Option<String>,
    pub tag_ids: Option<Vec<String>>,
}

#[derive(Clone, Debug, Deserialize)]
pub struct MultiUpdateParamsItem {
    pub id: String,
    pub name: Option<String>,
    pub subject_id: Option<String>,
    pub category_id: Option<String>,
    pub content: Option<String>,
    pub tag_ids: Option<Vec<String>>,
}

#[derive(Clone, Debug, Deserialize)]
pub struct MultiUpdateParams {
    pub condition: Option<SearchParams>,
    pub update: Option<UpdateParams>,
    pub items: Option<Vec<MultiUpdateParamsItem>>,
}

#[derive(Clone, Debug, Default, Deserialize, Serialize)]
pub struct Knowledge {
    pub id: String,

    #[serde(skip_serializing_if = "Option::is_none")]
    pub owner_id: Option<String>,

    #[serde(skip_serializing_if = "Option::is_none")]
    pub category_id: Option<String>,

    #[serde(skip_serializing_if = "Option::is_none")]
    pub subject_id: Option<String>,

    #[serde(skip_serializing_if = "Option::is_none")]
    pub name: Option<String>,

    #[serde(skip_serializing_if = "Option::is_none")]
    pub content: Option<String>,

    #[serde(skip_serializing_if = "Option::is_none")]
    pub created_at: Option<i64>,

    #[serde(skip_serializing_if = "Option::is_none")]
    pub modified_at: Option<i64>,
}

impl Knowledge {
    pub fn from_model(entity: &Model) -> Self {
        Knowledge {
            id: id2str(entity.id),
            name: entity.name.clone(),
            content: entity.content.clone(),
            created_at: entity.created_at,
            modified_at: entity.modified_at,
            owner_id: opt_id2str(&entity.owner_id),
            category_id: opt_id2str(&entity.category_id),
            subject_id: opt_id2str(&entity.subject_id),
        }
    }

    pub fn to_model(&self) -> Result<Model> {
        Ok(Model {
            id: str2id(&self.id)?,
            name: self.name.clone(),
            content: self.content.clone(),
            created_at: self.created_at,
            modified_at: self.modified_at,
            owner_id: opt_str2id(&self.owner_id)?,
            category_id: opt_str2id(&self.category_id)?,
            subject_id: opt_str2id(&self.subject_id)?,
        })
    }
}

#[derive(Deserialize, Debug, Default, Clone)]
pub struct SearchParams {
    #[serde(default)]
    pub name: Option<String>,

    #[serde(default)]
    pub category_id: Option<String>,

    #[serde(default)]
    pub subject_id: Option<String>,

    #[serde(default)]
    pub offset: Option<u64>,

    #[serde(default)]
    pub limit: Option<u64>,

    #[serde(default)]
    pub order_by: Option<String>,

    #[serde(default)]
    pub order: Option<String>,

    #[serde(default, deserialize_with = "deserialize_strs")]
    pub tag_ids: Option<Vec<String>>,

    #[serde(default, deserialize_with = "deserialize_strs")]
    pub cols: Option<Vec<String>>,
}

#[get("/api/v1/knowledge")]
async fn list_knowledge(req: HttpRequest, query_params: web::Query<SearchParams>) -> Result<ApiResponse, ApiError> {
    let knowledge_search_params = query_params.into_inner();
    trace!("list_knowledge query: {:?}", knowledge_search_params);
    let user_token = get_user_from_request(&req)?;
    let (knowledge, total) = srv_knowledge::search(&user_token, &knowledge_search_params).await?;
    Ok(ApiResponse::datas(knowledge, total))
}

#[post("/api/v1/knowledge")]
async fn create_knowledge(req: HttpRequest, req_body: String) -> Result<ApiResponse, ApiError> {
    trace!("create_knowledge body: {}", req_body);
    let user_token = get_user_from_request(&req)?;
    let new_knowledge: CreateParams = serde_json::from_str(&req_body)?;

    let knowledge = srv_knowledge::create_knowledge(&user_token, &new_knowledge).await?;
    Ok(ApiResponse::data(knowledge))
}

#[put("/api/v1/knowledge")]
async fn multi_update_knowledge(req: HttpRequest, req_body: String) -> Result<ApiResponse, ApiError> {
    trace!("multi_update_knowledge body: {}", req_body);
    let user_token = get_user_from_request(&req)?;
    let update_params: MultiUpdateParams = serde_json::from_str(&req_body)?;

    let knowledge = srv_knowledge::multi_update_knowledge(&user_token, &update_params).await?;
    Ok(ApiResponse::data(knowledge))
}

#[get("/api/v1/knowledge/{id}")]
async fn get_knowledge(req: HttpRequest, path_params: web::Path<String>) -> Result<ApiResponse, ApiError> {
    let knowledge_id = path_params.into_inner();
    trace!("get_knowledge path: {:?}", knowledge_id);
    let user_token = get_user_from_request(&req)?;
    let result = srv_knowledge::find_knowledge_by_id(&user_token, &knowledge_id).await?;
    Ok(ApiResponse::data(result))
}

#[delete("/api/v1/knowledge/{id}")]
async fn delete_knowledge(req: HttpRequest, path_params: web::Path<String>) -> Result<ApiResponse, ApiError> {
    trace!("delete_knowledge path: {:?}", path_params);
    let user_token = get_user_from_request(&req)?;
    let knowledge_id = path_params.into_inner();
    let result = srv_knowledge::delete_knowledge_by_id(&user_token, &knowledge_id).await?;
    Ok(ApiResponse::data(result))
}

#[put("/api/v1/knowledge/{id}")]
async fn update_knowledge(req: HttpRequest, req_body: String, path_params: web::Path<String>) -> Result<ApiResponse, ApiError> {
    trace!("update_knowledge path: {:?}, body: {}", path_params, req_body);
    let user_token = get_user_from_request(&req)?;
    let new_knowledge: UpdateParams = serde_json::from_str(&req_body)?;

    let knowledge_id = path_params.into_inner();
    let result = srv_knowledge::update_knowledge_by_id(&user_token, &knowledge_id, &new_knowledge).await?;
    Ok(ApiResponse::data(result))
}

#[get("/api/v1/knowledge/{id}/tags")]
async fn list_knowledge_tags(req: HttpRequest, path_params: web::Path<String>) -> Result<ApiResponse, ApiError> {
    trace!("list_knowledge_tags path: {:?}", path_params);
    let user_token = get_user_from_request(&req)?;
    let knowledge_id = path_params.into_inner();
    let result = srv_knowledge::list_tags_by_id(&user_token, &knowledge_id).await?;
    Ok(ApiResponse::data(result))
}

pub fn config(cfg: &mut web::ServiceConfig) {
    info!("Configuring knowledge APIs");
    cfg.service(list_knowledge);
    cfg.service(create_knowledge);
    cfg.service(multi_update_knowledge);
    cfg.service(get_knowledge);
    cfg.service(delete_knowledge);
    cfg.service(update_knowledge);
    cfg.service(list_knowledge_tags);
}
